package cx3k.dataAccess;

// don't touch ProfileManager, DatabaseManager

import java.io.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.w3c.dom.*;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.stream.StreamResult;

import org.apache.xerces.dom.DOMImplementationImpl;
import org.apache.xerces.jaxp.*;
import org.apache.xerces.parsers.*;
import org.apache.xml.serialize.XMLSerializer;
import org.apache.xml.serialize.OutputFormat;
import org.apache.commons.io.FileUtils;

import cx3k.scheduler.objects.AcademicYear;
import cx3k.scheduler.objects.Course;
import cx3k.scheduler.objects.Schedule;
import cx3k.scheduler.objects.ScheduledSemester;
import cx3k.scheduler.objects.Semester;
import cx3k.scheduler.objects.util.CompareUtil;
import cx3k.scheduler.wrappers.CourseWrapper;


/*
 * Note: This class is incomplete and needs to be finished.
 * @author charles
 * 
 * Progress: getting closer to a working copy, XML documentation is a pain for apache.
 * References:  http://www.w3.org/TR/2004/REC-xml-20040204/
 */
public class ScheduleManager extends FileManager<Schedule> {

	// XML tag's
	private static final String TAG_SCHEDULES      	= "schedules";
	private static final String TAG_SCHEDULE      	= "schedule";
	private static final String TAG_SEMESTER       	= "semester";
	private static final String TAG_CLASS    		= "class";
	
	// XML Settings
	private static final String XML_VERSION        	= "1.0";
	private static final String XML_ENCODING       	= "UTF-8";
	
	private Document xmlDoc = null;
	private String   xmlStr = null;
	
	/**
	 * Create a new instance of the ScheduleManager object.
	 * @param sourceFile is a type File pointing to an XML file for read, write, append, and delete operations.
	 */
	public ScheduleManager(File sourceFile) {
		super(sourceFile);
	}

	/**
	 * Delete the specified id of a schedule in loaded XML file if it exists.
	 */
	@Override
	public Schedule delete(int id) { // method works, writes an entire new file over old one
		// return deleted schedule?
	    //  create a DOM parser
		DOMParser parser = new DOMParser();
		Document document = null;
		NodeList schedules;
		Node selectedSchedule = null;
		boolean fileChanged = false;
		 //  parse the document  
		 try {
		        parser.parse(super.getSourceFile().getName());
		        document = parser.getDocument();
		        
		    } catch (IOException e) {
		        System.err.println (e);
		    } catch (SAXException e) {
				e.printStackTrace();
			}
		    
		    if(document == null)
		    	return null;
		
		    schedules = document.getElementsByTagName(TAG_SCHEDULE);
		    
		    for(int i = 0; i < schedules.getLength(); i++){
		    	int tempID = new Integer(schedules.item(i).getAttributes().getNamedItem("id").getNodeValue()).intValue();
		    	if(id == tempID){
		    		System.out.println("delete with id: " + id );
		    		selectedSchedule = schedules.item(i);
		    		fileChanged = true;
		    		break;
		    	}
		    }
		    if(!fileChanged)
		    	return null;
		    
		    if(selectedSchedule != null){ // fix, removes </schedules> when deleting last schedule in file
		    	document.getFirstChild().removeChild(selectedSchedule);
		    }
		    
		    xmlDoc = document;
		    System.out.println(this.generateXMLString(xmlDoc));
		    
		    // write new file
			try {
				FileUtils.writeStringToFile(super.getSourceFile(), this.generateXMLString(xmlDoc));
			} catch (IOException e) {
				e.printStackTrace();
				return null;
			}
		
		return null;
	}

	/**
	 * Look up the id for the specified Schedule object and pass that to Schedule delete(int id).
	 */
	@Override
	public void delete(Schedule delete) { // method works
		if(delete == null)
			return;	
		this.delete(delete.getScheduleId());
	}

	/**
	 * Load the specified id of a schedule from loaded XML file and return that Schedule object.
	 */
	@Override
	public Schedule load(int id) { // method works
		try {
			xmlStr = FileUtils.readFileToString(super.getSourceFile());
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
		return ParseXML(xmlStr, id);
	}
	
	/**
	 * Load all schedules from loaded XML file and return a list of Schedule objects.
	 */
	@Override
	public List<Schedule> loadAll() { // method works
	    //  create a DOM parser
		DOMParser parser = new DOMParser();
		Document document = null;
		NodeList schedules;
		List <Schedule> listOfSchedules = new ArrayList<Schedule>();
		
		 //  parse the document  
		 try {
		        parser.parse(super.getSourceFile().getName());
		        document = parser.getDocument();
		        
		    } catch (IOException e) {
		        System.err.println (e);
		    } catch (SAXException e) {
				e.printStackTrace();
			}
		    
		    if(document == null)
		    	return null;
		
		    schedules = document.getElementsByTagName(TAG_SCHEDULE);
		    
		    for(int i = 0; i < schedules.getLength(); i++){
		    	listOfSchedules.add(this.load(new Integer(schedules.item(i).getAttributes().getNamedItem("id").getNodeValue()).intValue()));
		    	//System.out.println("loadall with id: " + listOfSchedules.get(i).getScheduleId());
		    }		
		return listOfSchedules;
	}

	/**
	 * Save schedule using generated data in the GUI of the program.
	 */
	@Override
	public boolean save(Schedule save) { // method incomplete
	  	Element schedules = null;
	    Element schedule = null;
	    Element semester = null;
	    Element cclass = null;
	    NodeList schedule_nodes = null;
	    NodeList semester_nodes = null;
	    Set<AcademicYear> academicYears = null;
	    boolean NEW_FILE = false;
	    boolean APPEND_FILE = false;
	    xmlDoc = null;

	    if(save == null)
	    	return false;
	    		
		try {
			DocumentBuilderFactory dbFactory =
		        DocumentBuilderFactoryImpl.newInstance();
			DocumentBuilder docBuilder = dbFactory.newDocumentBuilder();
			xmlDoc = docBuilder.parse(super.getSourceFile());
			APPEND_FILE = true; // TODO - fix it
			
			System.out.println("XMLdoc: " + xmlDoc);
		} catch (SAXException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	    
		if(!APPEND_FILE){
			NEW_FILE = true;
		    try {
		      //Create a XML Document
		      DocumentBuilderFactory dbFactory =
		        DocumentBuilderFactoryImpl.newInstance();
		      DocumentBuilder docBuilder = dbFactory.newDocumentBuilder();
		      xmlDoc = docBuilder.newDocument();
		    } catch(Exception e) {
		        System.out.println("Error " + e);
		        return false;
		    }
		}
		
	    if(NEW_FILE){ // new empty XML file
	    	// Create the root element
	    	Date currentTime = new Date();
		    schedules = xmlDoc.createElement(TAG_SCHEDULES);
	    	xmlDoc.appendChild(xmlDoc.createComment("created on: " + new Date()));
		    
		    // adjust profile ID creation, look to database that value is unique?
		    schedules.setAttribute("profile", new Integer(Math.abs((int) currentTime.getTime())).toString());	   
	    }
	    else{ // XML file already exists with root
	    	System.out.println(xmlDoc);
	    	schedules = (Element) xmlDoc.getElementsByTagName(TAG_SCHEDULES).item(0);
	    }
	       
	    if(NEW_FILE){ // Add to the root Element
	    	xmlDoc.appendChild(schedules);
		}
	    //if(APPEND_FILE && !xmlDoc.getElementById(TAG_SCHEDULE).getAttributes().getNamedItem("id").getNodeValue().equals(new Integer(save.getScheduleId()).toString())){
	    //	System.out.println("id already in file!");
	    //}
	    
	    
	    if(NEW_FILE){  // Writing to a new file
		    schedule = xmlDoc.createElement(TAG_SCHEDULE);
		    schedule.setAttribute("id", new Integer(save.getScheduleId()).toString());
		    schedule.setAttribute("name", save.getScheduleName());
		    schedules.appendChild(schedule);
		    System.out.println("New file");
	    }
	    else{ // Appending to an existing file
	    	NodeList scheduleList = xmlDoc.getElementsByTagName(TAG_SCHEDULE);
	    	//System.out.println(xmlDoc.getE);
	    	boolean INLIST = false;
	    	for(int i = 0; i < scheduleList.getLength(); i++){
	    		if(scheduleList.item(i).getAttributes().getNamedItem("id").getNodeValue().equals(new Integer(save.getScheduleId()).toString())){
	    			INLIST = true;	// Schedule object already in xml file, may need to update fields?
	    			schedule = (Element) scheduleList.item(i);
	    		}	
	    	}
	    	if(!INLIST){// New schedule object being added to xml file
	    		System.out.println("new schedule object!");
			    schedule = xmlDoc.createElement(TAG_SCHEDULE);
			    schedule.setAttribute("id", new Integer(save.getScheduleId()).toString());
			    schedule.setAttribute("name", save.getScheduleName());
			    schedules.appendChild(schedule);
			    NEW_FILE = true;
	    	}
	    }
	    	
	    //System.out.println("Schedule: " + schedule.getTextContent());
	    schedule_nodes = schedules.getChildNodes();
    
	    academicYears = save.getAcademicYear();  
	    Iterator<AcademicYear> IterAcademic = academicYears.iterator();
	    
	    while(IterAcademic.hasNext()){  // go through AcademicYears, need to fix AcademicYear to give an actual year
	    	AcademicYear currentAcademicYear = IterAcademic.next();
	    	Collection<ScheduledSemester> semesters = currentAcademicYear.getSemesters();
	    	
	    	Iterator<ScheduledSemester> IterSemester = semesters.iterator();
	    	boolean flag_newSemester = false;
	    	
	    	while(IterSemester.hasNext()){ // go through each ScheduledSemester of an AcademicYear
	    		ScheduledSemester currentSemester = IterSemester.next();
	    		
	    		if(NEW_FILE){
		    		semester = xmlDoc.createElement(TAG_SEMESTER);
		    		semester.setAttribute("term", currentSemester.getTerm());
		    		semester.setAttribute("year", new Integer(currentSemester.getYear()).toString());
	    		}else{
	    			NodeList tempSemesters = xmlDoc.getElementsByTagName(TAG_SEMESTER);
	    			boolean found = false;
	    			
	    			for(int i = 0; i < tempSemesters.getLength(); i++){
	    				if((tempSemesters.item(i).getAttributes().getNamedItem("term").getNodeValue().equalsIgnoreCase(currentSemester.getTerm()) &&
	    					tempSemesters.item(i).getAttributes().getNamedItem("year").getNodeValue().equals(new Integer(currentSemester.getYear()).toString()))){
	    					found = false;
	    				}
	    				else{
	    					flag_newSemester = true;
	    					found = true;
	    					semester = (Element) tempSemesters.item(i);
	    					break;
	    				}
	    					
	    			}
	    			if(found){
    					semester = xmlDoc.createElement(TAG_SEMESTER);
    		    		semester.setAttribute("term", currentSemester.getTerm());
    		    		semester.setAttribute("year", new Integer(currentSemester.getYear()).toString());
    		    		
	    			}
	    			
	    		}
	    		
	    		Collection<Course> courses = currentSemester.getCourses();
	    		Iterator<Course> IterCourse = courses.iterator();
	    		
	    		while(IterCourse.hasNext()){ // go through each Course of a ScheduledSemester
	    			Course currentCourse = IterCourse.next();
	    			if(NEW_FILE){
		    			cclass = xmlDoc.createElement(TAG_CLASS);
		    			cclass.setAttribute("id", new Integer(currentCourse.getCourseId()).toString());
		    			cclass.setTextContent(currentCourse.getDegreeProgram() + currentCourse.getCourseNum());
		    			semester.appendChild(cclass);
	    			}
	    			else{
	    				NodeList tempCourses = xmlDoc.getElementsByTagName(TAG_CLASS);
	    				boolean found = false;
	    				
		    			for(int i = 0; i < tempCourses.getLength(); i++){
		    				if(!tempCourses.item(i).getAttributes().getNamedItem("id").getNodeValue().equals(new Integer(currentCourse.getCourseId()).toString())){
		    					found = false;
		    				}else{
		    					cclass = (Element) tempCourses.item(i);
		    					found = true;
		    					break;
		    				}
		    			}
		    			
		    			if(!found){
			    			cclass = xmlDoc.createElement(TAG_CLASS);
			    			cclass.setAttribute("id", new Integer(currentCourse.getCourseId()).toString());
			    			cclass.setTextContent(currentCourse.getDegreeProgram() + currentCourse.getCourseNum());
		    			}
	    			}
	    		}
	    		
	    		//System.out.println(semester.getTextContent());
	    		if(NEW_FILE || flag_newSemester)
	    			schedule.appendChild(semester);
	
	    	}
	    }
	    
		try {
			FileUtils.writeStringToFile(super.getSourceFile(), this.generateXMLString(xmlDoc));  //generate string and write to file
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
		return false;
	}
	
	/**
	 * 
	 * @param xmlStr
	 * @param id
	 * @return
	 */
	private Schedule ParseXML(String xmlStr, int id){ // method works, needs fixing to work with new scheduled item	
	    //  create a DOM parser
		DOMParser parser = new DOMParser();
		Document document = null;
		int index = -1;
		String scheduleId = "";
		NodeList schedules;
		NodeList schedule;
		NodeList semesters;
		NodeList classes;
		Node selectedSchedule;
		Node selectedSemester;
		Node selectedClass;
		Schedule theSchedule = null;
		AcademicYear theAcademicYear = null;
		List <Course> courseList = new ArrayList<Course>();
		
		 //  parse the document  
		 try {
		        parser.parse(super.getSourceFile().getName());
		        document = parser.getDocument();
		        
		    } catch (IOException e) {
		        System.err.println (e);
		    } catch (SAXException e) {
				e.printStackTrace();
			}
		    
		    if(document == null)
		    	return null;
		    
		    schedules = document.getChildNodes().item(0).getChildNodes();
	        selectedSchedule = schedules.item(0);
	        String profileID = document.getElementsByTagName(TAG_SCHEDULES).item(0).getAttributes().getNamedItem("profile").getNodeValue();
	        
	        //Element selectedSemester = document.getElementById(new Integer(id).toString());
	        semesters = document.getElementsByTagName(TAG_SCHEDULE);
	        
	        for(int i = 0; i < semesters.getLength(); i++){
	        	scheduleId = semesters.item(i).getAttributes().getNamedItem("id").getNodeValue();

	        	if(scheduleId.equals(new Integer(id).toString())){
		        	String name = semesters.item(i).getAttributes().getNamedItem("name").getNodeValue();
	        		//System.out.println("\n***********************************************");
	        		//System.out.print("\nSchedule ID: " + test);
	        		//System.out.println("\tName: " + name);
	    
	        		theSchedule = new Schedule(new Integer(profileID).intValue(),
	        					  		new Integer(scheduleId).intValue(), 
	        					  		name);
	        		index = i;
	        	}
	        }
	        
	        if(index == -1){
	        	return null;
	        }
	        
	        semesters = semesters.item(index).getChildNodes();
	        //System.out.println("Val: " + children.item(1).getAttributes().getNamedItem("term"));
	        selectedSemester = semesters.item(0);
	        
    		int startYear = 0;
    		int endYear = 0; 
    		int year = 0;
    		String term = "";
    		String startTerm = "";
    		boolean StartYear = false;
    		ScheduledSemester tempSemester = null;
			AcademicYear tempAcademicYear = null;

	        
	        while(selectedSemester != null){
	        	if(!selectedSemester.getNodeName().equals("#text")){
	        		year = new Integer(selectedSemester.getAttributes().getNamedItem("year").getNodeValue()).intValue();
	        		term = selectedSemester.getAttributes().getNamedItem("term").getNodeValue();
	        		if(!StartYear){
	        			startYear = year;
	        			StartYear = true;
	        			startTerm = term;
	        			tempAcademicYear = new AcademicYear(startYear, startYear + 1);	        			
	        		}
	        		tempSemester = new ScheduledSemester(year, term);
	        		
	        		// 
	        		ArrayList <Course> coursesOfSemester = new ArrayList<Course>();
	        		//System.out.print("Term: " + term);
	        		//System.out.println("\tYear: " + year);
	        		endYear = year;
	        		classes = selectedSemester.getChildNodes();
	        		selectedClass = classes.item(0);
	        		//CourseWrapper tempCourseWrap = null;
	        		
	        		while(selectedClass != null){
	        			
	        			if(!selectedClass.getNodeName().equals("#text"))
	        			{	        				
	        				Course tempCourse = new Course();
	        				Integer classId = new Integer(selectedClass.getAttributes().getNamedItem("id").getNodeValue());
	        				String  className = selectedClass.getTextContent();
	        				
	        				//tempCourse.setSemester(tempSemester);
	        				tempCourse.setCourseId(classId.intValue());
	        				tempCourse.setDegreeProgram(className.substring(0, 4));
	        				tempCourse.setCourseNum(className.substring(4, className.length()));
	        				tempCourse.setCredits("0");
	        				coursesOfSemester.add(tempCourse);		        				
	        				//tempCourseWrap = new CourseWrapper(tempCourse);
	        				//tempCourseWrap.setSemester(year, term, tempCourse);
	        				//System.out.print("\tClassID: " + classId.intValue());
	        				//System.out.println("\tClassName: " + className);
	        			}
	        			selectedClass = selectedClass.getNextSibling();
	        		}
	        		//System.out.println("startYear: " + startYear + " - " + "endYear: " + endYear);
        			tempSemester.setSemesterId(new Integer(scheduleId).intValue());
        			tempSemester.addCourses(coursesOfSemester);
        			tempAcademicYear.addSemester(tempSemester);
        			
        			// TODO - FIX
	        		if(CompareUtil.compareYears(startYear, endYear) == -1 && 
	        				(CompareUtil.compareTerms(term, startTerm) == -1 &&
	        				 CompareUtil.compareTerms(term, "SPRING") == 1	))
	        		{
	        			theSchedule.addAcademicYear(tempAcademicYear);
	        			startYear = endYear;
	        			StartYear = false;
	        		}
	        			
	        	}	
	        	selectedSemester = selectedSemester.getNextSibling();
	        	//System.out.println(children.item(i).getAttributes().getNamedItem("year"));
	        }
	        //theSchedule.setCourses(courseList);
	        //theSchedule.printCourses();
		return theSchedule;
	}

	

	  // Generate String out of the XML document object
	private String generateXMLString(Node document) { // method works, need to replace with something better

	   // StringWriter  strWriter    = null;
	    //XMLSerializer probeMsgSerializer   = null;
	    //OutputFormat  outFormat    = null;
	    DOMImplementationRegistry registry = null;
	    String data;
	    
		try {
			registry = DOMImplementationRegistry.newInstance();
		} catch (ClassCastException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	    DOMImplementationLS impl = 
	        (DOMImplementationLS)registry.getDOMImplementation("LS");

	    LSSerializer writer = impl.createLSSerializer();
	    DOMConfiguration config = writer.getDomConfig();
	    LSOutput output = impl.createLSOutput();
	    //config.setParameter("xml-declaration", xmlDecl);
	    config.setParameter("xml-declaration", true);
	    config.setParameter("format-pretty-print", true);  
	    config.setParameter("well-formed", true);
	    config.setParameter("comments", true);
	    //config.setParameter("encoding", this.XML_ENCODING);
	    
	    /* Parameters:
				      	canonical-form
						cdata-sections
						check-character-normalization
						comments
						datatype-normalization
						element-content-whitespace
						entities
						infoset
						namespaces
						namespace-declarations
						split-cdata-sections
						validate
						validate-if-schema
						well-formed
						discard-default-content
						format-pretty-print
						ignore-unknown-character-denormalizations
						xml-declaration
						error-handler
	     */
	    output.setEncoding(XML_ENCODING);
	    output.setByteStream(System.out);
	    writer.write(document, output);
	    document.normalize();

	    return writer.writeToString(document); 
	  }
}
